package messageprocessors;

import java.util.Observable;

import org.apache.log4j.Logger;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;

import errors.DSError;

import util.DSPropertyManager;
import messages.DSMessage;
import messages.DSMessageType;

public abstract class MessageProcessor extends Observable {
	
	private static Logger log = Logger.getLogger(MessageProcessor.class);
	
	
	protected boolean status;
	protected String id;
	protected String xmppPassword;
	protected String xmppServeradress;
	protected String xmppAdressSuffix;
	protected int xmppServerport;
	protected String javaServerId;
	public Connection connection;
	protected String xmppServerRealm;
	protected DSMessage lastMessageReceived;
	protected DSMessage lastMessageSent;
	protected DSError lastErrorRetrieved;
	
	protected Chat outChat;
	
	public MessageProcessor(String id, String xmppPassword) {
		this.id = id;
		this.xmppPassword = xmppPassword;
		status = false;
		
		//load from properties file
		javaServerId = DSPropertyManager.getInstance().getStringValue("javaserverid", "javaserver");
		xmppServeradress = DSPropertyManager.getInstance().getStringValue("xmppserveradress", "127.0.0.1");
		xmppServerport = DSPropertyManager.getInstance().getIntValue("xmppserverport", 5222);
		xmppServerRealm = DSPropertyManager.getInstance().getStringValue("xmppserverrealm", "testrealm");
		xmppAdressSuffix = DSPropertyManager.getInstance().getStringValue("xmppadresssuffix", "127.0.0.1");
	}
	
	
	public abstract void processMessage(DSMessage message) throws Exception;
	
	public void connect() throws Exception {
		this.connectToXmppServer();
	}
	
	public void disconnect() {
		this.disconnectFromXmppServer();
	}
	
	
	/**
	 * Sends a message to the specified receiver
	 * just specify the id, the domain is attached by the function automatically
	 * @param message the message to send
	 * @param targetId the target id without @domain/realm
	 * @throws Exception
	 */
	public void sendMessage(DSMessage message, String targetId) throws Exception {
		
		if(targetId.contains("@")) {
			log.error("Invalid target address id specified!");
			throw new Exception("The specified id already contains an @");
		}
		
		//generate target adress
		String adress = targetId + "@" + this.xmppAdressSuffix + "/" + this.getXmppServerRealm();
		
		log.debug("Sending message from: " + this.getId() + ", to: " + targetId + ", with message: " + message);
		
		Message myMessage = new Message();
		myMessage.setProperty("dsmessage", message);
		
		//if no chat is open --> open a new one
		// if a chat with another participant is open --> open a new one
		if(outChat == null || !outChat.getParticipant().equals(adress)) {
			outChat = connection.getChatManager().createChat(adress, new MessageListener() {
				
				@Override
				public void processMessage(Chat arg0, Message arg1) {
					try {
						MessageProcessor.this.processMessage(extractDSMessageFromMessage(arg1));
					} catch (Exception e) {
						log.error("An error occured while processing a DSMessage: " + e.getMessage());
					}
					
				}
			});
		} 
		
		//send message
		outChat.sendMessage(myMessage);
		this.setLastMessageSent(message);
	}
	
	/**
	 * sends the given message to the server for further processing
	 * @param msg
	 * @throws Exception
	 */
	public void sendMessageToJavaServer(DSMessage msg) throws Exception {
		sendMessage(msg, javaServerId);
	}

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
		setChanged();
		this.notifyObservers(status);
	}

	public String getId() {
		return id;
	}
	
	public String getXmppPassword() {
		return xmppPassword;
	}


	public String getXmppServeradress() {
		return xmppServeradress;
	}


	public int getXmppServerport() {
		return xmppServerport;
	}
	
	

	public String getXmppServerRealm() {
		return xmppServerRealm;
	}


	public void setXmppServerRealm(String xmppServerRealm) {
		this.xmppServerRealm = xmppServerRealm;
	}


	protected void connectToXmppServer() {
		try {
			
			ConnectionConfiguration conf = new ConnectionConfiguration(this.xmppServeradress, this.xmppServerport);
			//configure further options here
			
			Connection.DEBUG_ENABLED = true;
			
			connection = new XMPPConnection(conf);
			connection.connect();
			
			connection.login(this.id, this.xmppPassword, this.xmppServerRealm);
			
			//set presence
			Presence presence = new Presence(Presence.Type.available);
			presence.setStatus("Online");
			
			connection.sendPacket(presence);
			
			this.setStatus(true);
			
			//wait for incoming messages
			connection.getChatManager().addChatListener(new ChatManagerListener() {
				
				@Override
				public void chatCreated(Chat chat, boolean createdLocally) {
					if(!createdLocally) {
						
						//TODO: TEST THIS !!!!
						outChat = chat;
						chat.addMessageListener(new MessageListener() {
							
							@Override
							public void processMessage(Chat arg0, Message msg) {
								
								//log.debug("Incoming message is being processed!");
								
								if(msg!=null) {
									try {
										DSMessage receivedMsg = extractDSMessageFromMessage(msg);
										MessageProcessor.this.setLastMessageReceived(receivedMsg);
										MessageProcessor.this.processMessage(receivedMsg);
									} catch (Exception e) {
										log.error("An error occured while processing a DSMessage: " + e.getMessage());
										
									}
								} else {
									log.error("The received message is null!");
								}
								
							}
						});
					}
				}
			});
			
			log.debug(this.getId() + ": Connection to xmpp server established!");
			
			
			
			
		} catch (Exception e) {
			log.error("An error occured while connecting to the xmpp server: " + e.getMessage());
		}
	}
	
	protected void disconnectFromXmppServer() {
		if(connection!=null) {
			if(connection.isConnected()) {
				//set presence
				Presence presence = new Presence(Presence.Type.unavailable);
				presence.setStatus("Offline");
				
				connection.sendPacket(presence);
				
				connection.disconnect();
				connection = null;
				outChat = null;
				this.setStatus(false);
			}
		}
	}
	
	protected DSMessage extractDSMessageFromMessage(Message msg) {
		if(msg==null) {
			log.error("A null object was retrieved, but a message was excepted!");
			return null;
		}
		if(msg.getError()!=null) {
			log.error("The message delivered an error: "+ msg.getError().getCondition() + ", Code: " + msg.getError().getCode());
		}
		
		return (DSMessage) msg.getProperty("dsmessage");
	}
	
	/**
	 * generates a DSMessage with the according values
	 * DSMessage doesn't contain a hashmap already -> fill it before sending
	 * @param targetId the target id 
	 * @param originId the origin id
	 * @param type the message type
	 * @return an initialized DSMessage
	 */
	public static DSMessage generateDSMessage(String targetId, String originId, DSMessageType type) {
		DSMessage msg = new DSMessage();
		msg.setOriginId(originId);
		msg.setTargetId(targetId);
		msg.setType(type);
		return msg;
	}
	
	public DSMessage getLastMessageReceived() {
		return this.lastMessageReceived;
	}
	
	public DSMessage getLastMessageSent() {
		return this.lastMessageSent;
	}
	
	public void setLastMessageReceived(DSMessage msg) {
		this.lastMessageReceived = msg;
		this.setChanged();
		this.notifyObservers(msg);
	}
	
	public void setLastMessageSent(DSMessage msg) {
		this.lastMessageSent = msg;
		this.setChanged();
		this.notifyObservers(msg);
	}
	
	public void setLastErrorRetrieved(DSError err) {
		this.lastErrorRetrieved = err;
		this.setChanged();
		this.notifyObservers(err);
	}
	
	public DSError getLastErrorRetrieved() {
		return this.lastErrorRetrieved;
	}
}
